package jmine.tec.report.impl.table;

import java.io.Serializable;
import java.util.Iterator;

import jmine.tec.report.api.RendererParameters;
import jmine.tec.report.api.RendererTarget;
import jmine.tec.report.api.table.ReportCell;
import jmine.tec.report.api.table.ReportColumn;
import jmine.tec.report.api.table.ReportTable;
import jmine.tec.report.impl.ReportBuilder;
import jmine.tec.utils.collection.Iterators;

/**
 * Builder para auxiliar na criação de relatórios de tabela.
 * 
 * @author lundberg
 * @param <V> tipo de valor da tabela
 */
public class ReportTableBuilder<V> implements Serializable {

    // Tipos padrão de renderers.

    /**
     * Tipo de colunas com valor numérico.
     */
    public static final String TYPE_NUMBER = "number";

    /**
     * Tipo de colunas com valor numérico em formato de porcentagem.
     */
    public static final String TYPE_PORCENTAGEM = "numberPorcentagem";

    /**
     * Tipo de colunas com valor do tipo java.util.date.
     */
    public static final String TYPE_DATE = "date";

    /**
     * Tipo de colunas com valor do tipo timestamp.
     */
    public static final String TYPE_TIMESTAMP = "timestamp";

    /**
     * Tipo de colunas com valor string.
     */
    public static final String TYPE_STRING = "string";

    private Integer groupIndex;

    private ReportTableImpl<V> table;

    private final ReportBuilder reportBuilder;

    /**
     * Construtor
     * 
     * @param reportBuilder reportBuilder
     * @param valueClass valueClass
     * @param id id
     */
    public ReportTableBuilder(ReportBuilder reportBuilder, Class<V> valueClass, String id) {
        this.reportBuilder = reportBuilder;
        this.groupIndex = 0;
        this.table = new ReportTableImpl<V>(id);
    }

    /**
     * Termina o builder e adiciona a tabela ao report builder.
     */
    public void close() {
        this.reportBuilder.addElement(this.createTable());
    }

    /**
     * Retorna a tabela que foi criada
     * 
     * @return ReportTable
     */
    public ReportTable<V> createTable() {
        ReportTable<V> t = this.table;
        this.table = null;
        return t;
    }

    /**
     * Define o iterator de registros da tabela.
     * 
     * @param recordIterator recordIterator
     * @deprecated Utilize setRecords(Iterable)
     */
    @Deprecated
    public void setRecordIterator(Iterator<V> recordIterator) {
        this.setRecords(Iterators.collectToList(recordIterator));
    }

    /**
     * Define os registros da tabela
     * 
     * @param records records
     */
    public void setRecords(Iterable<V> records) {
        this.table.setRecords(records);
    }

    /**
     * Adiciona uma coluna vazia.
     * 
     * @param id id
     * @param name name
     * @return a coluna criada.
     */
    public ReportColumn<V> addEmptyStringColumn(String id, String name) {
        return this.addStringColumn(id, name, new CellValueResolver<V>() {
            public Object resolveCellValue(V rowValue) {
                return null;
            }
        });
    }

    /**
     * Adiciona uma coluna vazia do tipo data.
     * 
     * @param id id
     * @param name name
     * @return a coluna criada.
     */
    public ReportColumn<V> addEmptyDateColumn(String id, String name) {
        return this.addDateColumn(id, name, new CellValueResolver<V>() {
            public Object resolveCellValue(V rowValue) {
                return null;
            }
        });
    }

    /**
     * Adiciona uma coluna vazia do tipo number.
     * 
     * @param id id
     * @param name name
     * @param decimals o número de casas decimais.
     * @return a coluna criada.
     */
    public ReportColumn<V> addEmptyNumberColumn(String id, String name, Integer decimals) {
        return this.addNumberColumn(id, name, new CellValueResolver<V>() {
            public Object resolveCellValue(V rowValue) {
                return null;
            }
        }, decimals);
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo string.
     * 
     * @param id id
     * @param name name
     * @param property property
     * @return a coluna criada.
     */
    public ReportColumn<V> addStringColumn(String id, String name, String property) {
        return this.addStringColumn(id, name, new PropertyCellValueResolver<V>(property));
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo string.
     * 
     * @param id id
     * @param name name
     * @param resolver resolver
     * @return a coluna criada.
     */
    public ReportColumn<V> addStringColumn(String id, String name, CellValueResolver<V> resolver) {
        ReportColumn<V> column = new ReportColumnImpl<V>(id, resolver);
        column.setHeader(new ReportCellImpl(name));
        this.createRPI(column.getHeader()).setIsHeader(true).setColumnType(TYPE_STRING);
        column.setRendererType(TYPE_STRING);
        this.table.addColumn(column);
        return column;
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo date.
     * 
     * @param id id
     * @param name name
     * @param property property
     * @return a coluna criada.
     */
    public ReportColumn<V> addDateColumn(String id, String name, String property) {
        return this.addDateColumn(id, name, new PropertyCellValueResolver<V>(property));
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo date.
     * 
     * @param id id
     * @param name name
     * @param resolver resolver
     * @return a coluna criada.
     */
    public ReportColumn<V> addDateColumn(String id, String name, CellValueResolver<V> resolver) {
        ReportColumn<V> column = new ReportColumnImpl<V>(id, resolver);
        column.setHeader(new ReportCellImpl(name));
        this.createRPI(column.getHeader()).setIsHeader(true).setColumnType(TYPE_DATE);
        column.setRendererType(TYPE_DATE);
        this.table.addColumn(column);
        return column;
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo timestamp.
     * 
     * @param id id
     * @param name name
     * @param property property
     */
    public void addTimestampColumn(String id, String name, String property) {
        this.addTimestampColumn(id, name, new PropertyCellValueResolver<V>(property));
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo timestamp.
     * 
     * @param id id
     * @param name name
     * @param resolver resolver
     */
    public void addTimestampColumn(String id, String name, CellValueResolver<V> resolver) {
        ReportColumn<V> column = new ReportColumnImpl<V>(id, resolver);
        column.setHeader(new ReportCellImpl(name));
        this.createRPI(column.getHeader()).setIsHeader(true).setColumnType(TYPE_TIMESTAMP);
        column.setRendererType(TYPE_TIMESTAMP);
        this.table.addColumn(column);
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo Number.
     * 
     * @param id id
     * @param name name
     * @param property property
     * @param decimals decimals
     * @return a coluna criada.
     */
    public ReportColumn<V> addNumberColumn(String id, String name, String property, Integer decimals) {
        return this.addNumberColumn(id, name, new PropertyCellValueResolver<V>(property), decimals);
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo Porcentagem.
     * 
     * @param id id
     * @param name name
     * @param property property
     * @param decimals decimals
     * @return a coluna criada.
     */
    public ReportColumn<V> addPorcentagemColumn(String id, String name, String property, Integer decimals) {
        return this.addPorcentagemColumn(id, name, new PropertyCellValueResolver<V>(property), decimals);
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo Number.
     * 
     * @param id id
     * @param name name
     * @param resolver resolver
     * @param decimals decimals
     * @return a coluna criada.
     */
    public ReportColumn<V> addNumberColumn(String id, String name, CellValueResolver<V> resolver, Integer decimals) {
        ReportColumn<V> column = new ReportColumnImpl<V>(id, resolver);
        column.setHeader(new ReportCellImpl(name));
        this.createRPI(column.getHeader()).setIsHeader(true).setColumnType(TYPE_NUMBER);
        column.setRendererType(TYPE_NUMBER);
        if (decimals != null) {
            this.createRPI(column).setDecimals(decimals);
        }
        this.table.addColumn(column);
        return column;
    }

    /**
     * Adiciona uma nova coluna referente a um campo do tipo Porcentagem.
     * 
     * @param id id
     * @param name name
     * @param resolver resolver
     * @param decimals decimals
     * @return a coluna criada.
     */
    public ReportColumn<V> addPorcentagemColumn(String id, String name, CellValueResolver<V> resolver, Integer decimals) {
        ReportColumn<V> column = new ReportColumnImpl<V>(id, resolver);
        column.setHeader(new ReportCellImpl(name));
        this.createRPI(column.getHeader()).setIsHeader(true).setColumnType(TYPE_NUMBER);
        column.setRendererType(TYPE_PORCENTAGEM);
        if (decimals != null) {
            this.createRPI(column).setDecimals(decimals);
        }
        this.table.addColumn(column);
        return column;
    }

    /**
     * Adiciona ao rodapé uma célula contendo a label dada.
     * 
     * @param columnId columnId
     * @param label label
     */
    public void addFooterLabel(String columnId, String label) {
        ReportColumn<V> column = this.findColumn(columnId);
        ReportCell footer = new ReportCellImpl(label);
        this.createRPI(footer).setIsFooter(true);
        footer.setRendererType(TYPE_STRING);
        column.setFooter(footer);
    }

    /**
     * Define que a coluna do o id dado deve permitir que o usuário ordene clicando no cabeçalho, no<br/>
     * caso de representações que permitem isso (atualmente JSF)
     * 
     * @param columnId columnId
     * @param sortColumn sortColumn
     */
    public void addSorting(String columnId, String sortColumn) {
        ReportColumn<V> column = this.findColumn(columnId);
        this.createRPI(column.getHeader()).setSortColumn(sortColumn);
    }

    /**
     * Define que a coluna do o id dado deve permitir que o usuário ordene clicando no cabeçalho, no<br/>
     * caso de representações que permitem isso (atualmente JSF). Além disso, a coluna deve ser ordenada<br/>
     * por default, antes mesmo que o usuário peça.
     * 
     * @param columnId columnId
     * @param sortColumn sortColumn
     * @param asc se a ordem é crescente (descrescente caso contrário)
     */
    public void addSortingDefault(String columnId, String sortColumn, boolean asc) {
        ReportColumn<V> column = this.findColumn(columnId);
        this.createRPI(column.getHeader()).setSortColumn(sortColumn).setDefaultSort(asc);
    }

    /**
     * Adiciona um label ao cabeçalho da tabela.
     * 
     * @param label label
     */
    public void addTableHeader(String label) {
        ReportCell cell = new ReportCellImpl(label);
        this.createRPI(cell).setIsTitle(true);
        this.table.addTableHeader(cell);
    }

    /**
     * Esconde a coluna com o id dado.
     * 
     * @param columnId columnId
     * @param hide hide
     */
    public void setHideColumn(String columnId, boolean hide) {
        ReportColumn<V> column = this.findColumn(columnId);
        this.createRPI(column).setHidden(hide);
    }

    /**
     * Cria um builder de grupo de registros.
     * 
     * @return ReportTableGroupBuilder
     */
    public ReportTableGroupBuilder<V> createGroupBuilder() {
        return new ReportTableGroupBuilder<V>(this.table, this.groupIndex++);
    }

    /**
     * Encontra a coluna com o id dado.
     * 
     * @param columnId columnId
     * @return ReportColumn
     */
    public ReportColumn<V> findColumn(String columnId) {
        for (ReportColumn<V> column : this.table.getColumns()) {
            if (column.getId().equals(columnId)) {
                return column;
            }
        }
        throw new IllegalStateException();
    }

    /**
     * Cria um RendererParameterInterface para o target
     * 
     * @param target target
     * @return RendererParameterInterface
     */
    private RendererParameterInterface createRPI(RendererTarget target) {
        return new RendererParameterInterface(target);
    }

    /**
     * Retorna os parâmetros da tabela.
     * 
     * @return parâmetros da tabela.
     */
    public RendererParameters getTableParameter() {
        return this.table.getParameters();
    }
}
